package com.cysyz.mylib.excel.impl;

import java.io.File;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import com.cysyz.mylib.excel.IExcelReader;
import com.cysyz.mylib.excel.SheetHeader;
import com.cysyz.mylib.excel.annotation.ExcelField;
import com.cysyz.mylib.excel.engine.CellEngine;
import com.cysyz.mylib.excel.engine.ExcelEngine;
import com.cysyz.mylib.excel.engine.SheetEngine;
import com.cysyz.mylib.excel.exception.ExcelException;
import com.cysyz.mylib.excel.validator.FileNameValidator;
import com.cysyz.mylib.excel.validator.SheetNameValidator;

public class ExcelReaderImpl implements IExcelReader {
	
	private ExcelEngine excelEngine;

	/*
	 * public static <T> List<T> importExcel(Class<T> clazz, File excelFile,
	 * List<SheetHeader> sheetHeaders) { return ExcelUtil.importExcel(clazz,
	 * null, excelFile, sheetHeaders); }
	 */

	/** 
	 * @Title: importExcel
	 * @Author: 崔勇
	 * @Date 2014-4-23 下午4:03:26
	 * @param clazz
	 * @param sheetName
	 * @param excelFile
	 * @param sheetHeaders
	 * @param fileNameValidator
	 * @param sheetNameValidator
	 * @return
	 * @throws ExcelException
	 */
	@Override
	public <T> Map<String, List<T>> importExcel(
			Class<T> clazz,
			String sheetName, 
			File excelFile, 
			List<SheetHeader> sheetHeaders,
			FileNameValidator fileNameValidator, 
			SheetNameValidator sheetNameValidator) throws ExcelException {
		String excelFileName = excelFile.getName();
		List<String> errorList = new ArrayList<String>(); // 所有的错误信息存储在这里
		// 校验文件名称
		validateFileName(fileNameValidator, excelFileName);
		
		//key代表每个sheet，value代表每个sheet中的所有数据
		Map<String, List<T>> sheetNameAndRowsList = new HashMap<String, List<T>>();
		
		List<SheetEngine> waitedToReadSheets = new ArrayList<SheetEngine>();  
		
		configWaitedToReadSheets(sheetName, excelEngine, waitedToReadSheets);
		
		//key代表excel中列的索引，如0，1，2。value代表其对应的类的属性。
		Map<Integer, Field> fieldsMap = matchEexcelColToField(clazz);
		
		//校验所有等待被读取的sheet中的表头
		validateAllSheetHeaders(waitedToReadSheets, sheetHeaders);
		
		int sheetIndex = 0;
		String errorMessage = null;
		for (SheetEngine sheet : waitedToReadSheets) {
			String currentSheetName = sheet.getName();
			//校验sheetName
			if (sheetNameValidator != null && (errorMessage = sheetNameValidator.sheetNameValidate(sheetIndex ++, currentSheetName)) != null) {
				errorList.add(errorMessage);
			}
			
			List<T> oneSheetContents = null;
			try {
				oneSheetContents = readOneSheet(clazz, sheet, fieldsMap, sheetHeaders);
			} catch (ExcelException e) {
				errorList.addAll(e.getErrorList());
				continue;
			}
			sheetNameAndRowsList.put(currentSheetName, oneSheetContents);
		}
		if (!errorList.isEmpty()) {
			throw new ExcelException(errorList);
		}
		return sheetNameAndRowsList;
	}

	private void validateAllSheetHeaders(List<SheetEngine> waitedToReadSheets,
			List<SheetHeader> sheetHeaders) throws ExcelException {
		List<String> errorList = new ArrayList<String>();
		for (SheetEngine sheet : waitedToReadSheets) {
			errorList.addAll(validateSheetHeader(sheet, sheetHeaders));
		}
		
		if (!errorList.isEmpty()) {
			throw new ExcelException(errorList);
		}
		
	}

	/**
	 * 配置准备读取的sheets，如果sheetName部位null，那么就只读取此sheet，否则读取excel中所有的sheet
	 * @Title: configWaitedToReadSheets
	 * @Author: 崔勇
	 * @Date 2014-4-14 上午9:25:06
	 * @param sheetName
	 * @param book
	 * @param waitedToReadSheets
	 */
	private  void configWaitedToReadSheets(String sheetName,
			ExcelEngine book, List<SheetEngine> waitedToReadSheets) {
		if (sheetName != null && !sheetName.trim().equals("")) {
			waitedToReadSheets.add(book.getSheet(sheetName));// 如果指定sheet名,则取指定sheet中的内容.
		} else if (sheetName == null) { //如果没有sheet名,则读取全部的sheet中的内容
			for (int sheetIdx = 0 ; sheetIdx < book.getNumberOfSheets(); sheetIdx ++) {
				waitedToReadSheets.add(book.getSheet(sheetIdx));
			}
		}
	}

	private static void validateFileName(FileNameValidator fileNameValidator,
			String excelFileName) throws ExcelException {
		List<String> errorList = new ArrayList<String>();
		String errorMessage = null;
		if (fileNameValidator != null && (errorMessage = fileNameValidator.fileNameValidate(excelFileName)) != null) {
			errorList.add(errorMessage);
			throw new ExcelException(errorList);
		}
	}

	/**
	 * 验证sheet的header
	 * @Title: validateSheetHeader
	 * @Author: 崔勇	cysyz1229@gmail.com
	 * @Version: V1.00
	 * @Date Apr 6, 2014 9:09:28 AM
	 * @param sheet 
	 * @param sheetHeaders
	 * @return 错误消息的集合
	 * @throws ExcelException 
	 */
	private  List<String> validateSheetHeader(SheetEngine sheet,
			List<SheetHeader> sheetHeaders) {
		LinkedList<String> errorList = new LinkedList<String>();
		
		List<CellEngine> rowCells = sheet.getOneRowCells(0);
		List<String> inputRowHeaderNames = new ArrayList<String>();
		List<String> expectedHeaderNames = new ArrayList<String>();
		
		for (CellEngine cellEngine : rowCells) {
			inputRowHeaderNames.add(cellEngine.getContents());
		}
		for (SheetHeader sh : sheetHeaders) {
			expectedHeaderNames.add(sh.getHeaderName());
		}
		
		//表头个数一致性校验
		if (inputRowHeaderNames.size() != expectedHeaderNames.size()) {
			StringBuilder sb = new StringBuilder("工作表【");
			sb.append(sheet.getName());
			sb.append("】的表头名称错误:");
			sb.append("文件中的表头为");
			sb.append(inputRowHeaderNames);
			sb.append(",");
			sb.append("要求的表头为");
			sb.append(expectedHeaderNames);
			errorList.add(sb.toString());
			return errorList;
		}
		
		//表头对应一致性校验
		for (int i = 0 ; i < inputRowHeaderNames.size() ; i ++) {
			String inputHeaderName = inputRowHeaderNames.get(i);
			String expectedHeaderName = expectedHeaderNames.get(i);
			if (!inputHeaderName.equals(expectedHeaderName)) {
				errorList.add("工作表【"+sheet.getName()+"】的1行" +(i+1)+"列的标题应该是【"+expectedHeaderName+"】");
			}
		}
		if (!errorList.isEmpty()) {
			errorList.push("sheet标题行错误");
		}
		return errorList;
		
	}

	/**
	 * 根据类的属性上面的注解来找到excel中每一列对应的属性
	 * @Title: matchEexcelColToField
	 * @Author: 崔勇	
	 * @Date Apr 6, 2014 10:32:11 AM
	 * @param clazz
	 * @return 键为列索引，值为Field的Map
	 */
	private <T> Map<Integer, Field> matchEexcelColToField(Class<T> clazz) {
		Map<Integer, Field> fieldsMap = new HashMap<Integer, Field>();// 定义一个map用于存放列的序号和field.
		Field[] allFields = clazz.getDeclaredFields();// 得到类的所有field.
		for (Field field : allFields) {
			// 将有注解的field存放到map中.
			if (field.isAnnotationPresent(ExcelField.class)) {
				ExcelField attr = field
						.getAnnotation(ExcelField.class);
				int col = execelFormatColumnIndexToNumber(attr.column());// 获得列号
				// System.out.println(col + "====" + field.getName());
				field.setAccessible(true);// 设置类的私有字段属性可访问.
				fieldsMap.put(col, field);
			}
		}
		return fieldsMap;
	}

	/**
	 * 读取一个sheet中的内容
	 * @Title: readOneSheet
	 * @Author: 崔勇
	 * @Date 2014-4-14 上午9:35:00
	 * @param clazz
	 * @param sheet
	 * @param fieldsMap
	 * @param sheetHeaders
	 * @return
	 * @throws ExcelException
	 */
	private <T> List<T> readOneSheet(Class<T> clazz, SheetEngine sheet, Map<Integer, Field> fieldsMap, List<SheetHeader> sheetHeaders) throws ExcelException {
		List<T> voList = new ArrayList<T>();
		List<String> errorList = new ArrayList<String>();
		int rows = sheet.getRowCount();// 得到数据的行数
		if (rows > 0) {// 有数据时才处理
			
			try {
				for (int rowIndex = 1; rowIndex < rows; rowIndex++) {// 从第2行开始取数据,默认第一行是表头.
					T entity = readOneRow(clazz, sheet, fieldsMap,
							sheetHeaders, errorList, rowIndex);
					if (entity != null) {
						voList.add(entity);
					}
				}
				if(!errorList.isEmpty()) {
					throw new ExcelException(errorList);
				}
			} catch (NumberFormatException e) {
				e.printStackTrace();
			} catch (InstantiationException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			}
		}
		return voList;
	}

	private <T> T readOneRow(Class<T> clazz, SheetEngine sheet,
			Map<Integer, Field> fieldsMap, List<SheetHeader> sheetHeaders,
			List<String> errorList, int i) throws InstantiationException,
			IllegalAccessException {
		List<CellEngine> cells = sheet.getOneRowCells(i);// 得到一行中的所有单元格对象.
		T entity = null;
		for (int j = 0; j < fieldsMap.entrySet().size(); j++) {
			String cell = cells.get(j).getContents();// 单元格中的内容.
			SheetHeader sheetHeader = sheetHeaders.get(j);
			String errorMessage = null;
			if ((errorMessage = sheetHeader.validateCell(cell, i+1, j+1)) != null) {
				errorList.add(errorMessage);
			}
			if (!errorList.isEmpty()) {
				continue;
			}
			entity = (entity == null ? clazz.newInstance() : entity);// 如果不存在实例则新建.
			// System.out.println(cells[j].getContents());
			Field field = fieldsMap.get(j);// 从map中得到对应列的field.
			// 取得类型,并根据对象类型设置值.
			Class<?> fieldType = field.getType();
			if ((Integer.TYPE == fieldType)
					|| (Integer.class == fieldType)) {
				field.set(entity, Integer.parseInt(cell));
			} else if (String.class == fieldType) {
				field.set(entity, String.valueOf(cell));
			} else if ((Long.TYPE == fieldType)
					|| (Long.class == fieldType)) {
				field.set(entity, Long.valueOf(cell));
			} else if ((Float.TYPE == fieldType)
					|| (Float.class == fieldType)) {
				field.set(entity, Float.valueOf(cell));
			} else if ((Short.TYPE == fieldType)
					|| (Short.class == fieldType)) {
				field.set(entity, Short.valueOf(cell));
			} else if ((Double.TYPE == fieldType)
					|| (Double.class == fieldType)) {
				field.set(entity, Double.valueOf(cell));
			} else if (Character.TYPE == fieldType) {
				if ((cell != null) && (cell.length() > 0)) {
					field.set(entity, Character.valueOf(cell.charAt(0)));
				}
			}
		}
		return entity;
	}

	/**
	 * 将EXCEL中A,B,C,D,E列映射成0,1,2,3
	 * @Title: execelFormatColumnIndexToNumber
	 * @Author: 崔勇	
	 * @Date Apr 6, 2014 11:54:24 AM
	 * @param col
	 * @return
	 */
	private int execelFormatColumnIndexToNumber(String col) {
		col = col.toUpperCase();
		// 从-1开始计算,字母重1开始运算。这种总数下来算数正好相同。
		int count = -1;
		char[] cs = col.toCharArray();
		for (int i = 0; i < cs.length; i++) {
			count += (cs[i] - 64) * Math.pow(26, cs.length - 1 - i);
		}
		return count;
	}

	
	
	
	/**********getter and setter************/
	public ExcelEngine getExcelEngine() {
		return excelEngine;
	}

	public void setExcelEngine(ExcelEngine excelEngine) {
		this.excelEngine = excelEngine;
	}
	
	
}
